﻿using System.Collections.ObjectModel;
using System.Windows.Threading;

namespace PDBSmartMVVM.viewmodel
{
    /// <summary>
    /// Base class for all ViewModel classes displayed by TreeViewItems.  
    /// This acts as an adapter between a raw data object and a TreeViewItem.
    /// </summary>
    public class TreeViewItemViewModel : ViewModelBase
    {
        #region Data

        static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();

        readonly ObservableCollection<TreeViewItemViewModel> _children;
        readonly TreeViewItemViewModel _parent;

        bool _isExpanded;
        bool _isSelected;
        bool? _isChecked;

        private bool _isCalculated;

        public bool? IsChecked
        {
            get { return _isChecked; }
            set
            {
//                if (Parent != null && Parent.IsChecked == true)
//                    _isChecked = true;
//                else
                    this.SetIsChecked(value, true, true);
            }
        }



        public bool IsCalculated
        {
            get { return _isCalculated; }
            set
            {
                if (value != _isCalculated)
                {
//                    _isCalculated = value;
//                    foreach (var model in Children)
//                    {
//                        model.IsCalculated = _isCalculated;
//                    }
                    //this.OnPropertyChanged("IsCalculated");
                    SetIsCalculated(value, true, true);
                }
            }
        }

        void SetIsCalculated(bool value, bool updateChildren, bool updateParent)
        {
            if (value == _isCalculated)
                return;

            _isCalculated = value;

            if (updateChildren)
            {
                foreach (var child in Children)
                {
                    child.SetIsCalculated(_isCalculated, true, false);
                }
            }

            if (updateParent && _parent != null)
                _parent.VerifyCalculateState();

            this.OnPropertyChanged("IsCalculated");
        }

        void VerifyCalculateState()
        {
            bool state = false;
            for (int i = 0; i < this.Children.Count; ++i)
            {
                bool current = this.Children[i].IsCalculated;
                if (i == 0)
                {
                    state = current;
                }
                else if (state != current)
                {
                    state = false;
                    break;
                }
            }
            this.SetIsCalculated(state, false, true);
        }

        void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
        {
            if (value == _isChecked)
                return;

            _isChecked = value;

            if (updateChildren && _isChecked.HasValue)
            {
                foreach (var child in Children)
                {
                    child.SetIsChecked(_isChecked, true, false);
                }
            }
            //this.Children.ForEach(c => c.SetIsChecked(_isChecked, true, false));

            if (updateParent && _parent != null)
                _parent.VerifyCheckState();

            this.OnPropertyChanged("IsChecked");
        }

        void VerifyCheckState()
        {
            bool? state = null;
            for (int i = 0; i < this.Children.Count; ++i)
            {
                bool? current = this.Children[i].IsChecked;
                if (i == 0)
                {
                    state = current;
                }
                else if (state != current)
                {
                    state = null;
                    break;
                }
            }
            this.SetIsChecked(state, false, true);
        }

        #endregion // Data

        #region Constructors

        protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
        {
            _parent = parent;

            _children = new ObservableCollection<TreeViewItemViewModel>();

            if (lazyLoadChildren)
                _children.Add(DummyChild);
        }

        // This is used to create the DummyChild instance.
        private TreeViewItemViewModel()
        {
        }

        #endregion // Constructors

        #region Presentation Members

        #region Children

        /// <summary>
        /// Returns the logical child items of this object.
        /// </summary>
        public ObservableCollection<TreeViewItemViewModel> Children
        {
            get { return _children; }
        }

        #endregion // Children

        #region HasLoadedChildren

        /// <summary>
        /// Returns true if this object's Children have not yet been populated.
        /// </summary>
        public bool HasDummyChild
        {
            get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
        }

        #endregion // HasLoadedChildren

        #region IsExpanded

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is expanded.
        /// </summary>
        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if (value != _isExpanded)
                {

                    _isExpanded = value;

                    this.OnPropertyChanged("IsExpanded");
                }
                //DispatcherHelper.WaitForPriority(DispatcherPriority.ContextIdle);


                // Expand all the way up to the root.
                if (_isExpanded && _parent != null)
                    _parent.IsExpanded = true;

                // Lazy load the child items, if necessary.
                if (this.HasDummyChild)
                {
                    this.Children.Remove(DummyChild);
                    this.LoadChildren();
                }
            }
        }

        #endregion // IsExpanded

        #region IsSelected

        /// <summary>
        /// Gets/sets whether the TreeViewItem 
        /// associated with this object is selected.
        /// </summary>
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (value != _isSelected)
                {
                    _isSelected = value;
                    this.OnPropertyChanged("IsSelected");
                }
            }
        }

        #endregion // IsSelected

        #region LoadChildren

        /// <summary>
        /// Invoked when the child items need to be loaded on demand.
        /// Subclasses can override this to populate the Children collection.
        /// </summary>
        protected virtual void LoadChildren()
        {
        }

        #endregion // LoadChildren

        #region Parent

        public TreeViewItemViewModel Parent
        {
            get { return _parent; }
        }

        #endregion // Parent

        #endregion // Presentation Members


    }

    internal static class DispatcherHelper
    {
        /// <summary>
        /// This method allows developer to call a non-blocking wait. When calling WaitForPriority, the developer is certain
        /// that all dispatcher operations with priority higher than the one passed as a parameter will have been executed
        /// by the time the line of code that follows it is reached.
        /// Similar to VB's DoEvents.
        /// Keep in mind that this call does not guarantee that all operations at the priority passed will have been executed,
        /// only operations with priority higher than the one passed. In practice it waits for some (and sometimes all) 
        /// operations at the priority passed, but this is not guaranteed.
        /// </summary>
        /// <param name="priority">Priority below the one we want to wait for before the next line of code is executed.</param>
        internal static void WaitForPriority(DispatcherPriority priority)
        {
            DispatcherFrame frame = new DispatcherFrame();
            DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(priority, new DispatcherOperationCallback(ExitFrameOperation), frame);
            Dispatcher.PushFrame(frame);
            if (dispatcherOperation.Status != DispatcherOperationStatus.Completed)
            {
                dispatcherOperation.Abort();
            }
        }

        private static object ExitFrameOperation(object obj)
        {
            ((DispatcherFrame)obj).Continue = false;
            return null;
        }
    }
}